﻿using System;
using System.Threading;

namespace InfiniTec.Threading
{
	// NOTE: This is a value type so it works very efficiently when used as
	// a field in a class. Avoid boxing this or you will lose thread safety!
	public class SpinWaitLock
	{
		private const Int32 FreeStateValue = 0;
		private const Int32 OwnedStateValue = 1;
		private static readonly Boolean IsSingleCpuMachine = (Environment.ProcessorCount == 1);
		private Int32 _LockState; // Defaults to 0=FreeStateValue

		public void Enter()
		{
            Thread.BeginCriticalRegion();
            while (true)
            {
                // If resource available, set it to in-use and return
                if (Interlocked.Exchange(
                        ref _LockState, OwnedStateValue) == FreeStateValue)
                {
                    return;
                }

                // Efficiently spin, until the resource looks like it might 
                // be free. NOTE: Just reading here (as compared to repeatedly 
                // calling Exchange) improves performance because writing 
                // forces all CPUs to update this value
                while (Thread.VolatileRead(ref _LockState) == OwnedStateValue)
                {
                    StallThread();
                }
            }
		}
	
        public bool TryEnter(TimeSpan timeout)
		{
		    DateTime end;

            if (timeout == TimeSpan.MaxValue)
            {
                end = DateTime.MaxValue;
            }
            else
            {
                end = DateTime.Now + timeout;
            }

		    Thread.BeginCriticalRegion();
			while (DateTime.Now < end)
			{
				// If resource available, set it to in-use and return
				if (Interlocked.Exchange(
				    	ref _LockState, OwnedStateValue) == FreeStateValue)
				{
					return true;
				}

				// Efficiently spin, until the resource looks like it might 
				// be free. NOTE: Just reading here (as compared to repeatedly 
				// calling Exchange) improves performance because writing 
				// forces all CPUs to update this value
				while (Thread.VolatileRead(ref _LockState) == OwnedStateValue)
				{
					StallThread();
				}
			}
			Thread.EndCriticalRegion();
			return false;
			
		}

		public void Exit()
		{
			// Mark the resource as available
			Interlocked.Exchange(ref _LockState, FreeStateValue);
			Thread.EndCriticalRegion();
		}

		private static void StallThread()
		{
			if (IsSingleCpuMachine)
			{
				// On a single-CPU system, spinning does no good
				SafeNativeMethods.SwitchToThread();
			}
			else
			{
				// Multi-CPU system might be hyper-threaded, let other thread run
				Thread.SpinWait(1);
			}
		}

	}
}